// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_entity_particle_system.h"
#include "mk_error.h"
#include "mk_engine.h"
#include "mk_camera.h"
#include "mk_math.h"
#include "mk_resource_manager.h"
#include "mk_file_system.h"
#include "mk_scene_node.h"
#include "mk_particle.h"
#include "mk_particle_common.h"
#include "mk_particle_emitter.h"
#include "mk_particle_pool.h"
#include "mk_particle_renderer.h"
#include "mk_particle_emitter_modifier.h"
#include "mk_particle_modifier.h"
#include "mk_particle_modifier_color.h"
#include "mk_particle_modifier_size.h"

namespace MK3DE
{
	CEntityParticleSystem::CEntityParticleSystem(CScene* pScene)
		: CEntity(pScene)
	{
		_bEnable = TRUE;
		_texture = NULL;
		_textureCutting = 1;
	}

	CEntityParticleSystem::~CEntityParticleSystem()
	{
		__Clean();
	}

	void CEntityParticleSystem::__UpdateLocalAABB()
	{
		_localAABB.minPt = D3DXVECTOR3(-50, -50, -50);
		_localAABB.maxPt = D3DXVECTOR3(50, 50, 50);
	}

	void CEntityParticleSystem::__Clean()
	{
		std::vector<CParticleEmitter*>::iterator i = _vecEmitter.begin();
		while (i != _vecEmitter.end())
		{
			CParticleEmitter* emitter = *i;
			SAFE_DELETE(emitter);
			++i;
		}

		_vecEmitter.clear();

		SAFE_RELEASE(_texture);
	}

	BOOL CEntityParticleSystem::SetTextureName(const char* szTextureFileName)
	{
		if (!szTextureFileName || szTextureFileName[0] == '\0')
		{
			SAFE_RELEASE(_texture);
			return TRUE;
		}

		CTexture2D* texture = NULL;
		CResourceManager* resMgr = GetEngine()->GetResourceManager();
		resMgr->GetTexture2DFromFile(szTextureFileName, texture);
		if (texture)
		{
			SAFE_RELEASE(_texture);
			_texture = texture;
			return TRUE;
		}
		return FALSE;
	}

	std::string CEntityParticleSystem::GetTextureName()
	{
		if (_texture)
			return _texture->GetResourceName();
		return std::string();
	}

	BOOL CEntityParticleSystem::SetTextureCutting(DWORD dwCount)
	{
		if (dwCount == 0)
			return FALSE;

		_textureCutting = dwCount;
		return TRUE;
	}

	DWORD CEntityParticleSystem::GetTextureCutting()
	{
		return _textureCutting;
	}

	BOOL CEntityParticleSystem::LoadFromFile(const char* fileName)
	{
		BOOL ret = FALSE;
		int result = 1;
		TiXmlDocument* pXML = NULL;
		TiXmlElement* node = NULL;
		TiXmlElement* root = NULL;
		CMemoryBlock block;

		ret = GetEngine()->GetFileSystem()->ReadFile(fileName, &block);
		MK_PROCESS_ERROR(ret);

		pXML = new TiXmlDocument();
		MK_PROCESS_ERROR(pXML);

		pXML->SetCondenseWhiteSpace(false);
		pXML->Parse((const char*)block.GetBuffer());
		MK_PROCESS_ERROR(pXML->Error() == false);

		root = pXML->RootElement();
		MK_PROCESS_ERROR(root);

		result = strcmp(root->Value(), "ParticleSystem");
		MK_PROCESS_ERROR(result == 0);

		node = root->FirstChildElement("Texture");
		MK_PROCESS_ERROR(node);

		{
			DWORD textureCutting = 0;
			result = node->QueryIntAttribute("Cutting", (int*)&textureCutting);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			const char* szTextureName = node->Attribute("FileName");
			MK_PROCESS_ERROR(szTextureName);

			ret = SetTextureName(szTextureName);
			MK_PROCESS_ERROR(ret);

			ret = SetTextureCutting(textureCutting);
			MK_PROCESS_ERROR(ret);
		}

		node = root->FirstChildElement("Emitter");
		while (node)
		{
			CParticleEmitter* pEmitter = NULL;
			ret = AddEmitter(pEmitter);
			MK_PROCESS_ERROR(ret);

			ret = __LoadEmitter(pEmitter, node);
			MK_PROCESS_ERROR(ret);

			node = node->NextSiblingElement("Emitter");
		}

		ret = TRUE;
Exit0:
		SAFE_DELETE(pXML);

		return ret;
	}

	BOOL CEntityParticleSystem::__LoadEmitter(CParticleEmitter* pEmitter, TiXmlElement* node)
	{
		BOOL ret = FALSE;
		int result = -1;

		TiXmlElement* propNode = node->FirstChildElement();
		while (propNode)
		{
			if (strcmp(propNode->Value(), "CountMin") == 0)
			{
				float value;
				result = propNode->QueryFloatAttribute("Value", &value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetCountMin(value);
			}
			else if (strcmp(propNode->Value(), "CountMax") == 0)
			{
				float value;
				result = propNode->QueryFloatAttribute("Value", &value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetCountMax(value);
			}
			else if (strcmp(propNode->Value(), "ScatterMin") == 0)
			{
				float value;
				result = propNode->QueryFloatAttribute("Value", &value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetScatterMin(value);
			}
			else if (strcmp(propNode->Value(), "ScatterMax") == 0)
			{
				float value;
				result = propNode->QueryFloatAttribute("Value", &value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetScatterMax(value);
			}
			else if (strcmp(propNode->Value(), "RotationMin") == 0)
			{
				float value;
				result = propNode->QueryFloatAttribute("Value", &value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetRotationMin(value);
			}
			else if (strcmp(propNode->Value(), "RotationMax") == 0)
			{
				float value;
				result = propNode->QueryFloatAttribute("Value", &value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetRotationMax(value);
			}
			else if (strcmp(propNode->Value(), "LifeMin") == 0)
			{
				DWORD value;
				result = propNode->QueryIntAttribute("Value", (int*)&value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetLifeMin(value);
			}
			else if (strcmp(propNode->Value(), "LifeMax") == 0)
			{
				DWORD value;
				result = propNode->QueryIntAttribute("Value", (int*)&value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetLifeMax(value);
			}
			else if (strcmp(propNode->Value(), "SpeedMin") == 0)
			{
				float value;
				result = propNode->QueryFloatAttribute("Value", &value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetSpeedMin(value);
			}
			else if (strcmp(propNode->Value(), "SpeedMax") == 0)
			{
				float value;
				result = propNode->QueryFloatAttribute("Value", &value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetSpeedMax(value);
			}
			else if (strcmp(propNode->Value(), "ParticleSizeMin") == 0)
			{
				float x, y;
				result = propNode->QueryFloatAttribute("X", &x);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				result = propNode->QueryFloatAttribute("X", &y);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetParticleSizeMin(D3DXVECTOR2(x, y));
			}
			else if (strcmp(propNode->Value(), "ParticleSizeMax") == 0)
			{
				float x, y;
				result = propNode->QueryFloatAttribute("X", &x);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				result = propNode->QueryFloatAttribute("X", &y);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetParticleSizeMax(D3DXVECTOR2(x, y));
			}
			else if (strcmp(propNode->Value(), "SameWidthHeight") == 0)
			{
				BOOL value;
				result = propNode->QueryIntAttribute("Value", &value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetSameWidthHeight(value);
			}
			else if (strcmp(propNode->Value(), "TextureFrame") == 0)
			{
				DWORD value;
				result = propNode->QueryIntAttribute("Value", (int*)&value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetTextureFrame(value);
			}
			else if (strcmp(propNode->Value(), "ParticleBlendMode") == 0)
			{
				PARTICLE_RENDER_BLEND_MODE value;
				result = propNode->QueryIntAttribute("Value", (int*)&value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetParticleBlendMode(value);
			}
			else if (strcmp(propNode->Value(), "ShapeType") == 0)
			{
				PARTICLE_EMITTER_SHAPE_TYPE value;
				result = propNode->QueryIntAttribute("Value", (int*)&value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetShapeType(value);
			}
			else if (strcmp(propNode->Value(), "ShapeParam1") == 0)
			{
				float value;
				result = propNode->QueryFloatAttribute("Value", &value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetShapeParam1(value);
			}
			else if (strcmp(propNode->Value(), "ShapeParam2") == 0)
			{
				float value;
				result = propNode->QueryFloatAttribute("Value", &value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetShapeParam2(value);
			}
			else if (strcmp(propNode->Value(), "ShapeParam3") == 0)
			{
				float value;
				result = propNode->QueryFloatAttribute("Value", &value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetShapeParam3(value);
			}
			else if (strcmp(propNode->Value(), "ParticleType") == 0)
			{
				PARTICLE_TYPE value;
				result = propNode->QueryIntAttribute("Value", (int*)&value);
				MK_PROCESS_ERROR(result == TIXML_SUCCESS);

				pEmitter->SetParticleType(value);
			}
			else if (strcmp(propNode->Value(), "ParticleModifier") == 0)
			{
				ret = __LoadParticleModifier(pEmitter, propNode);
				MK_PROCESS_ERROR(ret);
			}

			propNode = propNode->NextSiblingElement();
		};

		ret = TRUE;
Exit0:
		return ret;
	}

	BOOL CEntityParticleSystem::__LoadParticleModifier(CParticleEmitter* pEmitter, TiXmlElement* node)
	{
		BOOL ret = FALSE;

		const char* modifierType = node->Attribute("Type");
		MK_PROCESS_ERROR(modifierType);

		if (strcmp(modifierType, "Color") == 0)
		{
			CParticleModifierColor* pModifier = NULL;
			ret = pEmitter->AddParticleModifier((CParticleModifier*&)pModifier, PMT_COLOR);
			MK_PROCESS_ERROR(ret);

			ret = __LoadParticleModifierColor(pModifier, node);
			MK_PROCESS_ERROR(ret);
		}
		else if (strcmp(modifierType, "Size") == 0)
		{
			CParticleModifierSize* pModifier = NULL;
			ret = pEmitter->AddParticleModifier((CParticleModifier*&)pModifier, PMT_SIZE);
			MK_PROCESS_ERROR(ret);

			ret = __LoadParticleModifierSize(pModifier, node);
			MK_PROCESS_ERROR(ret);
		}

		ret = TRUE;
Exit0:
		return ret;
	}

	BOOL CEntityParticleSystem::__LoadParticleModifierColor(CParticleModifierColor* pModifier, TiXmlElement* node)
	{
		BOOL ret = FALSE;

		TiXmlElement* keyFrameNode = node->FirstChildElement("KeyFrame");
		while (keyFrameNode)
		{
			int a, r, g, b, life;
			int result;
			result = keyFrameNode->QueryIntAttribute("a", &a);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = keyFrameNode->QueryIntAttribute("r", &r);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = keyFrameNode->QueryIntAttribute("g", &g);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = keyFrameNode->QueryIntAttribute("b", &b);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = keyFrameNode->QueryIntAttribute("Life", &life);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			DWORD color = MK_COLOR_ARGB(a, r, g, b);
			pModifier->SetKeyFrame(life, color);

			keyFrameNode = keyFrameNode->NextSiblingElement("KeyFrame");
		}

		ret = TRUE;
Exit0:
		return ret;
	}

	BOOL CEntityParticleSystem::__LoadParticleModifierSize(CParticleModifierSize* pModifier, TiXmlElement* node)
	{
		BOOL ret = FALSE;

		TiXmlElement* keyFrameNode = node->FirstChildElement("KeyFrame");
		while (keyFrameNode)
		{
			float w, h;
			int life;
			int result;
			result = keyFrameNode->QueryFloatAttribute("w", &w);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = keyFrameNode->QueryFloatAttribute("h", &h);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			result = keyFrameNode->QueryIntAttribute("Life", &life);
			MK_PROCESS_ERROR(result == TIXML_SUCCESS);

			pModifier->SetKeyFrame(life, D3DXVECTOR2(w, h));

			keyFrameNode = keyFrameNode->NextSiblingElement("KeyFrame");
		}

		ret = TRUE;
Exit0:
		return ret;
	}


	BOOL CEntityParticleSystem::AddEmitter(CParticleEmitter*& pEmitter)
	{
		pEmitter = new CParticleEmitter;
		MK_PROCESS_ERROR(pEmitter);

		_vecEmitter.push_back(pEmitter);
		return TRUE;
Exit0:
		return FALSE;
	}

	BOOL CEntityParticleSystem::RemoveEmitter(CParticleEmitter* pEmitter)
	{
		for (int i = 0; i < (int)_vecEmitter.size(); ++i)
		{
			if (_vecEmitter[i] == pEmitter)
			{
				SAFE_DELETE(pEmitter);
				_vecEmitter.erase(_vecEmitter.begin() + i);
				return TRUE;
			}
		}

		return FALSE;
	}

	CParticleEmitter* CEntityParticleSystem::GetEmitter(DWORD idx)
	{
		if (idx < (DWORD)_vecEmitter.size())
			return _vecEmitter[idx];
		return NULL;
	}

	void CEntityParticleSystem::Update(DWORD dt, BOOL bChanged)
	{
		if (!_bEnable || !_sceneNode)
			return;

		CEntity::Update(dt, bChanged);

		std::vector<CParticleEmitter*>::iterator i = _vecEmitter.begin();
		while (i != _vecEmitter.end())
		{
			CParticleEmitter* emitter = *i;
			emitter->Update(dt, _sceneNode->GetAbsoluteTransformation());

			++i;
		}
	}

	void CEntityParticleSystem::Render()
	{
		if (!_bVisible)
			return;

		DWORD particleCount = 0;
		std::vector<CParticleEmitter*>::iterator i = _vecEmitter.begin();
		while (i != _vecEmitter.end())
		{
			CParticleEmitter* emitter = *i;
			particleCount += emitter->GetParticleCount();        
			++i;
		}

		if (particleCount == 0)
			return;

		CEngine* engine = GetEngine();
		CCamera* camera = engine->GetCurrentCamera();
		D3DXMATRIX mvp = camera->GetViewProjMatrix();

		CParticleRenderer* particleRenderer = engine->_GetParticleRenderer();
		if (particleRenderer->PrepareRender(particleCount))
		{
			i = _vecEmitter.begin();
			while (i != _vecEmitter.end())
			{
				CParticleEmitter* emitter = *i;
				if (emitter->GetParticleBlendMode() == PRBM_SRC_ALPHA_DST_INVALPHA)
					emitter->FillVertexBuffer(_textureCutting);

				++i;
			}

			particleRenderer->RenderAll(_texture, mvp, PRBM_SRC_ALPHA_DST_INVALPHA);
		}

		if (particleRenderer->PrepareRender(particleCount))
		{
			i = _vecEmitter.begin();
			while (i != _vecEmitter.end())
			{
				CParticleEmitter* emitter = *i;
				if (emitter->GetParticleBlendMode() == PRBM_SRC_ALPHA_DST_ONE)
					emitter->FillVertexBuffer(_textureCutting);

				++i;
			}

			particleRenderer->RenderAll(_texture, mvp, PRBM_SRC_ALPHA_DST_ONE);
		}
	}
}